// Flickr auth and export support
// Copyright 2006 (C) Ralph Thomas

#include <apps/photo/flickr.h>
#include <util/http.h>
#include <util/xml.h>
#include <util/md5.h>
#include <curl/curl.h> // for hacky photo uploading :(
#include <map>
#include <stdexcept>

#define BASE_URL	"http://www.flickr.com/services/rest/?"
#define AUTH_URL	"http://www.flickr.com/services/auth/?"
#define UPLOAD_URL	"http://www.flickr.com/services/upload/"
#define API_KEY		"a9e95533fa8115da53c52675d0d0283d"
#define SHARED_SECRET	"ec96307902b87103"

namespace flickr {
	typedef std::map<std::string, std::string> flickr_request_t;
	static std::string make_signature( const flickr_request_t& r ) {
		std::string signature( SHARED_SECRET );
		for( flickr_request_t::const_iterator i = r.begin(); i != r.end(); i++ )
			signature += i->first + i->second;
		return util::md5( signature );
	}
	static std::string make_request_url( const std::string base_url, const flickr_request_t& request ) {
		//
		// Add api_key to the request.
		//
		flickr_request_t r( request );
		r["api_key"] = API_KEY;
		//
		// std::map is a sorted container, so we may always assume
		// that the arguments are sorted alphabetically here (which
		// is what flickr wants for the MD5 sum).
		//
		// We build the URL at the same time as generating the
		// signature.
		//
		std::string signature( SHARED_SECRET );
		std::string url( base_url );
		for( flickr_request_t::iterator i = r.begin(); i != r.end(); i++ ) {
			signature += i->first + i->second;
			//
			// XXX: perform URL escaping here!
			//
			url += i->first + "=" + i->second + "&";
		}
		signature = util::md5( signature );
		url += "api_sig=" + signature;
		return url;
	}
	static std::string make_request( const flickr_request_t& r ) {
		return util::http_get( make_request_url( BASE_URL, r ) );
	}
	//
	/// Check if something that came back from flickr is a failed URL.
	///
	/// \param document	the document that flickr returned.
	//
	static void check_failure( const util::xml_doc_t& document ) {
		TiXmlElement* root = document->RootElement();
		std::string stat = util::xpath_eval_string( root, "/rsp/@stat" );
		if( stat != "fail" ) return;
		//
		// We failed, throw an exception.
		//
		std::string msg = util::xpath_eval_string( root, "/rsp/err/@msg" );
		throw std::runtime_error( "flickr: " + msg );
	}
	std::string get_frob() {
		flickr_request_t r;
		r["method"] = "flickr.auth.getFrob";
		util::xml_doc_t doc( util::parse_xml( make_request( r ) ) );
		check_failure( doc );
		return util::xpath_eval_string( doc->RootElement(), "/rsp/frob/text()" );
	}
	std::string get_auth_url( const std::string& frob ) {
		flickr_request_t r;
		r["perms"] = "write";
		r["frob"] = frob;
		return make_request_url( AUTH_URL, r );
	}
	std::string get_token( const std::string& frob ) {
		flickr_request_t r;
		r["method"] = "flickr.auth.getToken";
		r["frob"] = frob;
		util::xml_doc_t doc( util::parse_xml( make_request( r ) ) );
		check_failure( doc );
		//
		// We must have write permissions.
		//
		TiXmlElement* root = doc->RootElement();
		std::string perms = util::xpath_eval_string( root, "//auth/perms/text()" );
		if( perms != "write" ) throw std::runtime_error( "flickr: couldn't get write permissions" );
		return util::xpath_eval_string( root, "//auth/token/text()" );
	}
	bool check_token( const std::string& token ) {
		flickr_request_t r;
		r["method"] = "flickr.auth.checkToken";
		r["auth_token"] = token;
		util::xml_doc_t doc( util::parse_xml( make_request( r ) ) );
		try { check_failure( doc ); } catch( ... ) { return false; }
		std::string perms = util::xpath_eval_string( doc->RootElement(), "//auth/perms/text()" );
		if( perms != "write" ) return false;
		return true;
	}
	void upload_photo( const std::string& token, const std::string& title, const std::string& description, const std::string& tags, bool is_public, bool is_friend, bool is_family, std::streambuf& photo ) {
		//
		// Initialize CURL for POSTing the picture. We also have to
		// read the entire photo in to memory at once (rather than
		// reading it as we send it, which would make much more sense).
		//
		CURL*			curl = NULL;
		struct curl_httppost*	formpost = NULL;
		struct curl_httppost*	lastptr = NULL;
		std::string		data;
		std::streamsize		read = 0;
		flickr_request_t	request;
		char			buffer[4096];

		curl_global_init( CURL_GLOBAL_ALL );
		//
		// Build the request we're going to send in the POST data to
		// flickr.
		//
		request["auth_token"] = token;
		request["api_key"] = API_KEY;
		request["title"] = title;
		request["description"] = description;
		request["tags"] = tags;
		request["is_public"] = (is_public ? "1" : "0");
		request["is_friend"] = (is_friend ? "1" : "0");
		request["is_family"] = (is_family ? "1" : "0");
		request["api_sig"] = make_signature( request );
		//
		// Read in our file.
		//
		do {
			read = photo.sgetn( buffer, 4096 );
			data += std::string( buffer, read );
		} while( read == 4096 );
		//
		// Build the CURL options.
		//
		for( flickr_request_t::iterator i = request.begin(); i != request.end(); i++ )
			curl_formadd( &formpost, &lastptr,
				CURLFORM_COPYNAME, i->first.c_str(),
				CURLFORM_NAMELENGTH, i->first.size(),
				CURLFORM_COPYCONTENTS, i->second.c_str(),
				CURLFORM_CONTENTSLENGTH, i->second.size(),
				CURLFORM_END );
		curl_formadd( &formpost, &lastptr,
			CURLFORM_COPYNAME, "photo",
			CURLFORM_BUFFER, "upload.jpg",
			CURLFORM_BUFFERPTR, data.c_str(),
			CURLFORM_BUFFERLENGTH, data.size(),
			CURLFORM_CONTENTTYPE, "image/jpeg", // or so we assume
			CURLFORM_END );

		curl = curl_easy_init();
		curl_easy_setopt( curl, CURLOPT_URL, UPLOAD_URL );
		curl_easy_setopt( curl, CURLOPT_HTTPPOST, formpost );
		bool failed = false;
		if( curl_easy_perform( curl ) ) failed = true;
		curl_easy_cleanup( curl );
		curl_formfree( formpost );

		if( failed ) throw std::runtime_error( "flickr: upload failed somehow" );
	}
}
